﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using ifunction.SqlDeployConsole.Core;
using System.Threading;
using System.Xml.Linq;
using System.Data.SqlClient;
using System.IO;
using ifunction.SqlDeployConsole.Properties;

namespace ifunction.SqlDeployConsole
{
    public sealed class SqlExecutor
    {
        bool isFull = false;
        Version baseVersion = null;
        Version targetVersion = null;
        ConnectionStringModel connectionModel = null;
        XDocument xDocument = null;
        XElement includedXml = null;

        static readonly string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Constants.ConfigurationFilePath);

        /// <summary>
        /// Executes the specified args.
        /// </summary>
        /// <param name="args">The args.      
        /// arg[0]: connection string
        /// arg[1]: type(Full or Incremental)
        /// OR
        /// arg[0]: type(Full or Incremental)
        /// </param>
        public void Execute(string[] args)
        {
            #region Load arguments
            try
            {

                if (args.Length >= 2)
                {
                    connectionModel = ConnectionStringModel.LoadLoadConnectionStringFromBase64(args[0]);
                    includedXml = XElement.Parse(DecodeFromBase64(args[1]));
                    isFull = includedXml.Name.LocalName == Constants.Full;
                }
                else
                {
                    includedXml = xDocument.Root.Element(Constants.Full);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to load parameters caused by: " + ex.Message);
                return;
            }

            #endregion

            #region Load xml

            try
            {
                xDocument = XDocument.Load(configPath);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to load config file caused by: " + ex.Message);
                return;
            }

            #endregion

            /// Read configuration values for later use.
            #region Read version

            try
            {
                baseVersion = isFull ? null : new Version(includedXml.Attribute(Constants.BaseVersion).Value);
                targetVersion = new Version(includedXml.Attribute(Constants.TargetVersion).Value);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to load version info caused by: " + ex.Message);
                return;
            }

            #endregion

            #region Load connection string from xml.
            ///If no connect string is provided by argument, try to load it from configuration.
            if (connectionModel == null)
            {
                try
                {
                    connectionModel = ConnectionStringModel.LoadConnectionString(xDocument.Root.Element(Constants.ConnectionString));
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Failed to load connection string from xml caused by: " + ex.Message);
                    return;
                }
            }

            #endregion

            #region Try to initialize SQL connection instance

            SqlConnection sqlConnection = null;
            try
            {
                sqlConnection = new SqlConnection(connectionModel.ToConnectionString());
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to initialize SQL connection instance caused by: " + ex.Message);
                return;
            }

            #endregion

            #region Try to match version for deployment

            try
            {
                string versionString = ScriptHelper.GetRequiredVersion(connectionModel);
                Version currentVersion = !string.IsNullOrWhiteSpace(versionString) ? new Version(versionString) : null;
                if (!isFull && currentVersion != null && currentVersion.CompareTo(baseVersion) != 0)
                {
                    Console.WriteLine("Unmatched version to deploy. Current:" + currentVersion.ToString() + ", Required:" + baseVersion.ToString());
                    return;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to match version caused by: " + ex.Message);
                return;
            }

            #endregion

            #region Prepare deployment scripts

            string fullOrIncremental = (isFull ? Constants.Full : Constants.Incremental);
            XElement rootNode = FindMatchNodeByVersions(xDocument.Root, includedXml);
            if (rootNode == null)
            {
                Console.WriteLine("Failed to load connection string from xml.");
            }
            string baseContainer = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fullOrIncremental);
            string targetContainer = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fullOrIncremental + "_" + (baseVersion == null ? string.Empty : baseVersion.ToString()) + "_" + targetVersion.ToString());
            if (!Directory.Exists(targetContainer))
            {
                Directory.CreateDirectory(targetContainer);
            }

            List<string> scriptNames = new List<string>();
            GenerateScripts(scriptNames, baseContainer, targetContainer, rootNode, Constants.Table, connectionModel.Database, includedXml);
            GenerateScripts(scriptNames, baseContainer, targetContainer, rootNode, Constants.Function, connectionModel.Database, includedXml);
            GenerateScripts(scriptNames, baseContainer, targetContainer, rootNode, Constants.View, connectionModel.Database, includedXml);
            GenerateScripts(scriptNames, baseContainer, targetContainer, rootNode, Constants.StoredProcedure, connectionModel.Database, includedXml);
            GenerateScripts(scriptNames, baseContainer, targetContainer, rootNode, Constants.Data, connectionModel.Database, includedXml);
            GenerateScripts(scriptNames, baseContainer, targetContainer, rootNode, Constants.BCP, connectionModel.Database, includedXml);

            #endregion

            Console.WriteLine();

            bool isSuccess = false;
            try
            {
                ScriptHelper.RunSqlScript(connectionModel, scriptNames);
                Console.WriteLine("Completed!");
                Console.WriteLine();
                isSuccess = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error occurred!");
                Console.WriteLine("----------------------------------------------");
                Console.WriteLine(ex.Message);
                Console.WriteLine();
            }


            if (isSuccess)
            {
                string operatorInfo = connectionModel.IsWindowsAuthentication ? System.Security.Principal.WindowsIdentity.GetCurrent().Name : connectionModel.UserName;
                ScriptHelper.WriteExecutionScriptLog(operatorInfo, connectionModel.ToConnectionString(), baseVersion, targetVersion, isFull);
                Console.WriteLine("SQL log has updated to:" + targetVersion.ToString());
            }
        }

        /// <summary>
        /// Decodes from base64.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        public static string DecodeFromBase64(string input)
        {
            string result = string.Empty;
            try
            {
                byte[] bytes = Convert.FromBase64String(input);
                result = System.Text.Encoding.UTF8.GetString(bytes);
            }
            catch
            {
            }
            return result;
        }

        /// <summary>
        /// Finds the match node by versions.
        /// </summary>
        /// <param name="configurationXml">The configuration XML.</param>
        /// <param name="includedXml">The included XML.</param>
        /// <returns></returns>
        public static XElement FindMatchNodeByVersions(XElement configurationXml, XElement includedXml)
        {
            XElement result = null;

            if (configurationXml != null && includedXml != null)
            {
                var nameMatchedItems = configurationXml.Elements(includedXml.Name.LocalName);
                if (nameMatchedItems.Count() > 1)
                {
                    foreach (var one in nameMatchedItems)
                    {
                        if (one.Attribute(Constants.BaseVersion).Value == includedXml.Attribute(Constants.BaseVersion).Value
                            && one.Attribute(Constants.TargetVersion).Value == includedXml.Attribute(Constants.TargetVersion).Value)
                        {
                            result = one;
                            break;
                        }
                    }
                }
                else
                {
                    result = nameMatchedItems.FirstOrDefault();
                }
            }

            return result;
        }

        /// <summary>
        /// Generates the scripts.
        /// </summary>
        /// <param name="scriptNames">The script names.</param>
        /// <param name="baseContainer">The base container.</param>
        /// <param name="targetContainer">The target container.</param>
        /// <param name="rootNode">The root node.</param>
        /// <param name="sectionName">Name of the section.</param>
        /// <param name="databaseName">Name of the database.</param>
        /// <param name="includedXml">The included XML.</param>
        private void GenerateScripts(List<string> scriptNames, string baseContainer, string targetContainer, XElement rootNode, string sectionName, string databaseName, XElement includedXml)
        {
            var node = rootNode.Element(sectionName);
            if (node == null)
            {
                Console.WriteLine("Skip to generate scripts for " + sectionName + " caused by no section node found.");
            }
            else
            {
                if (sectionName == Constants.BCP)
                {
                    if (connectionModel.IsWindowsAuthentication)
                    {
                        string path = ScriptHelper.GenerateBCPScript(baseContainer, targetContainer, node, databaseName, includedXml);
                        scriptNames.Add(path);
                        Console.WriteLine(sectionName + " path: " + path.GetStringValue(Constants.NA));
                    }
                    else
                    {
                        Console.WriteLine("BCP is only supported by Windows Authentication.");
                    }
                }
                else
                {
                    string path = ScriptHelper.GenerateScripts(baseContainer, targetContainer, node, databaseName, includedXml);
                    scriptNames.Add(path);
                    Console.WriteLine(sectionName + " path: " + path.GetStringValue(Constants.NA));
                }
            }
        }
    }
}
